/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Compilador.Semantico;

import Compilador.Sintatico.ArvoreSintatica.ArvoreSintatica;
import Compilador.Sintatico.ArvoreSintatica.NoArvoreSintatica;
import Compilador.Sintatico.ArvoreSintatica.TypeNo;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author Rafa
 */
public class AnalisadorSemantico {

    private ArvoreSintatica arvoreSintatica;
    private TabelaSemantica tabelaSemantica;
    private ArrayList<NoArvoreSintatica> nosVisitadosTerminais;
    private ArrayList<String> operadoresRelacionais;
    private ArrayList<String> operadoresAritmeticos;
    private ArrayList<String> operadoresBooleanos;

    public AnalisadorSemantico(ArvoreSintatica arvoreSintatica) throws SemanticoException {
        this.arvoreSintatica = arvoreSintatica;
        nosVisitadosTerminais = new ArrayList<NoArvoreSintatica>();
        inicializaTabela();
        inicializaOperadores();
    }

    private void inicializaTabela() throws SemanticoException {
        tabelaSemantica = new TabelaSemantica();


    }

    private void inicializaOperadores() {
        operadoresRelacionais = new ArrayList<String>();
        operadoresRelacionais.add("==");
        operadoresRelacionais.add("~=");
        operadoresRelacionais.add(">");
        operadoresRelacionais.add("<");
        operadoresRelacionais.add(">=");
        operadoresRelacionais.add("<=");
        operadoresBooleanos = new ArrayList<String>();
        operadoresBooleanos.add("and");
        operadoresBooleanos.add("or");
        operadoresBooleanos.add("not");
        operadoresAritmeticos = new ArrayList<String>();
        operadoresAritmeticos.add("+");
        operadoresAritmeticos.add("^");
        operadoresAritmeticos.add("-");
        operadoresAritmeticos.add("%");
        operadoresAritmeticos.add("/");
        operadoresAritmeticos.add("*");

    }

    //Método responsável por calcular o valor da expressao
    private TypeVar calculaValorExpressao(List<NoArvoreSintatica> nos, TypeVar type) throws SemanticoException {
        if (nos.isEmpty()) {
            return type;
        } else {
            NoArvoreSintatica no = nos.get(0);
            int count = 0;
            //Checa se tipo é table
            if (no.getNoDescricao().equals("{")) {
                while (!no.getNoDescricao().equals("}")) {
                    count++;
                    no = nos.get(count);
                }
                if (type == null) {
                    type = TypeVar.Table;
                } else {
                    if (type == TypeVar.Especial) {
                        type = TypeVar.Table;
                    }
                    if (type != TypeVar.Table) {
                        throw new SemanticoException("Tipos incompatíveis", no.getLinha(), no.getColuna(), no.getNoDescricao());
                    }
                }
                return calculaValorExpressao(nos.subList(count + 1, nos.size()), type);
            } else if (no.getNoDescricao().equals("(")) {
                int quantParenteses = 1;

                do {
                    count++;
                    no = nos.get(count);
                    if (no.getNoDescricao().equals("(")) {
                        quantParenteses++;
                    } else if (no.getNoDescricao().equals(")")) {
                        quantParenteses--;
                    }
                } while (!no.getNoDescricao().equals(")") || quantParenteses > 0);

                TypeVar typeV = calculaValorExpressao(nos.subList(1, count), null);
                return calculaValorExpressao(nos.subList(count + 1, nos.size()), typeV);

            } else if (operadoresAritmeticos.contains(no.getNoDescricao())) {
                if (type == TypeVar.Especial) {
                    type = TypeVar.Number;
                }
                if (type != TypeVar.Number) {
                    throw new SemanticoException("Operadores Artiméticos apenas aplicados a Number", no.getLinha(), no.getColuna(), no.getNoDescricao());
                } else {
                    TypeVar typeV = calculaValorExpressao(nos.subList(1, nos.size()), TypeVar.Number);

                    if (type != typeV) {
                        throw new SemanticoException("Tipos incompatíveis", no.getLinha(), no.getColuna(), no.getNoDescricao());

                    } else {
                        return typeV;
                    }
                }

            } else if (operadoresBooleanos.contains(no.getNoDescricao())) {
                if (no.getNoDescricao().equals("not")) {
                    type = TypeVar.Boolean;
                    return calculaValorExpressao(nos.subList(1, nos.size()), TypeVar.Boolean);
                } else {
                    if (type == TypeVar.Especial) {
                        type = TypeVar.Boolean;
                    }
                    if (type != TypeVar.Boolean) {
                        throw new SemanticoException("Operadores Booleanos apenas aplicados a Booleanos", no.getLinha(), no.getColuna(), no.getNoDescricao());
                    } else {
                        TypeVar typeV = calculaValorExpressao(nos.subList(1, nos.size()), TypeVar.Boolean);

                        if (type != typeV) {
                            throw new SemanticoException("Tipos incompatíveis", no.getLinha(), no.getColuna(), no.getNoDescricao());

                        } else {
                            return typeV;
                        }
                    }
                }
            } else if (operadoresRelacionais.contains(no.getNoDescricao())) {
                if (no.getNoDescricao().equals("~=")) {
                    TypeVar typeV = calculaValorExpressao(nos.subList(1, nos.size()), type);
                    if (typeV == type) {
                        return TypeVar.Boolean;
                    }
                } else {
                    if (type == TypeVar.Boolean || type == TypeVar.Table) {
                        throw new SemanticoException("Operadores relacionais somente aplicados a string e number", no.getLinha(), no.getColuna(), no.getNoDescricao());
                    }
                    TypeVar typeV = calculaValorExpressao(nos.subList(1, nos.size()), type);
                    if (typeV == type) {
                        return TypeVar.Boolean;
                    }
                }

            } else if (no.getTypeNo() == TypeNo.Number) {
                if (type == null) {
                    type = TypeVar.Number;
                } else {
                    if (type != TypeVar.Number) {
                        throw new SemanticoException("Tipos incompatíveis", no.getLinha(), no.getColuna(), no.getNoDescricao());
                    }
                }
                return calculaValorExpressao(nos.subList(count + 1, nos.size()), type);

            } else if (no.getTypeNo() == TypeNo.String) {
                if (type == null) {
                    type = TypeVar.String;
                } else {
                    if (type != TypeVar.String) {
                        throw new SemanticoException("Tipos incompatíveis", no.getLinha(), no.getColuna(), no.getNoDescricao());
                    }
                }
                return calculaValorExpressao(nos.subList(count + 1, nos.size()), type);

            } else if (no.getTypeNo() == TypeNo.Boolean) {

                if (type == null) {
                    type = TypeVar.Boolean;
                } else {
                    if (type != TypeVar.Boolean) {
                        throw new SemanticoException("Tipos incompatíveis", no.getLinha(), no.getColuna(), no.getNoDescricao());
                    }
                }
                return calculaValorExpressao(nos.subList(count + 1, nos.size()), type);

            } else if (no.getTypeNo() == TypeNo.Var) {
                AtributoTabela atributo = tabelaSemantica.getAtributo(no);
                if (atributo instanceof AtributoTabelaFunction) {
                    count = checkFunction(nos, no, count, atributo);
                    AtributoTabelaFunction att = (AtributoTabelaFunction) atributo;
                    if (type == null) {
                        type = att.getTypeRetorno();
                    } else {
                        if (type != att.getTypeRetorno()) {
                            throw new SemanticoException("Tipos incompatíveis", no.getLinha(), no.getColuna(), no.getNoDescricao());
                        }
                    }
                    return calculaValorExpressao(nos.subList(count + 1, nos.size()), type);

                } else {
                    if (type == null) {
                        type = atributo.getType();
                    } else {
                        if (atributo.getType() == TypeVar.Especial) {
                            atributo.setType(type);
                        }
                        if (type != atributo.getType()) {
                            throw new SemanticoException("Tipos incompatíveis", no.getLinha(), no.getColuna(), no.getNoDescricao());
                        }
                    }
                    return calculaValorExpressao(nos.subList(count + 1, nos.size()), type);
                }
            } else if (no.getNoDescricao().equals(".")) {
                if (type != TypeVar.Table) {
                    throw new SemanticoException("Esperado uma tabela", no.getLinha(), no.getColuna(), no.getNoDescricao());

                } else {
                    return calculaValorExpressao(nos.subList(count + 2, nos.size()), TypeVar.String); //considero o valor como string da tabela                    
                }
            } else if (no.getNoDescricao().equals("[")) {
                count++;
                no = nos.get(count);
                while (!no.getNoDescricao().equals("]")) {
                    count++;
                    no = nos.get(count);
                }
                return calculaValorExpressao(nos.subList(count + 1, nos.size()), TypeVar.String); //considero o valor como string da tabela                    

            }
            throw new SemanticoException("Tipos incompatíveis", no.getLinha(), no.getColuna(), no.getNoDescricao());
        }


    }

    //apenas analisa o for do tipo for v = e1,e2,e3
    private int analisaFor(int count) throws SemanticoException {
        count = count + 1;

        NoArvoreSintatica no = nosVisitadosTerminais.get(count);
        String nameVar = no.getNoDescricao();
        int linha = no.getLinha();
        int coluna = no.getColuna();
        //checa se é o for do tipo v = e1,e2,e3
        if (nosVisitadosTerminais.get(count + 1).getNoDescricao().equals("=")) {
            int quantParenteses = 0;
            ArrayList<TypeVar> types = new ArrayList<TypeVar>();
            ArrayList<NoArvoreSintatica> nosAnalisar = new ArrayList<NoArvoreSintatica>();
            count += 2;
            no = nosVisitadosTerminais.get(count);
            while (!no.getNoDescricao().equals("do")) {

                if (no.getNoDescricao().equals("(")) {
                    quantParenteses++;
                } else if (no.getNoDescricao().equals(")")) {
                    quantParenteses--;
                }
                if (no.getNoDescricao().equals(",") && quantParenteses == 0) {
                    types.add(calculaValorExpressao(nosAnalisar, null));
                    nosAnalisar.clear();
                } else {
                    nosAnalisar.add(no);
                }
                count++;
                no = nosVisitadosTerminais.get(count);

            }
            types.add(calculaValorExpressao(nosAnalisar, null));
            nosAnalisar.clear();
            for (int x = 0; x < types.size(); x++) {
                if (types.get(x) != TypeVar.Number) {
                    throw new SemanticoException("Tipo Number esperado para o comando for", linha, coluna, nameVar);
                }

            }
            AtributoTabela atributo = new AtributoTabela(TypeVar.Number, tabelaSemantica.getEscopoAtual(), linha, coluna, nameVar);
            tabelaSemantica.insertAtributo(atributo, true);
            return count - 1;
        } else {
            while (!no.getNoDescricao().equals("do")) {
                count++;
                no = nosVisitadosTerminais.get(count);
            }
            return count - 1;
        }
    }

    private int checkFunction(List<NoArvoreSintatica> nos, NoArvoreSintatica no, int count, AtributoTabela atributo) throws SemanticoException {
        int quantParenteses = 1;
        int numeroParametros = 0;
        count = count + 1;
        no = nos.get(count);
        do {
            count++;
            no = nos.get(count);
            if (no.getNoDescricao().equals("(")) {
                quantParenteses++;
            } else if (no.getNoDescricao().equals(")")) {
                quantParenteses--;
                if (!nos.get(count - 1).getNoDescricao().equals("(") && quantParenteses == 0) {
                    numeroParametros++;
                }
            }
            if (quantParenteses == 1 && no.getNoDescricao().equals(",")) {
                numeroParametros++;
            }

        } while (!no.getNoDescricao().equals(")") || quantParenteses > 0);
        AtributoTabelaFunction att = (AtributoTabelaFunction) atributo;
        if (att.getNumeroParametros() != numeroParametros) {
            throw new SemanticoException("Quantidade de parametros da função incompatível com sua declaração", no.getLinha(), no.getColuna(), no.getNoDescricao());
        }
        return count;
    }

    //analisa o if e retorna a nova posicao dos terminais visitados
    private int analisaIf(int count, String command) throws SemanticoException {
        count++;
        ArrayList<NoArvoreSintatica> nosIf = new ArrayList<NoArvoreSintatica>();
        NoArvoreSintatica noV = nosVisitadosTerminais.get(count);
        while (!noV.getNoDescricao().equals("then")) {
            nosIf.add(nosVisitadosTerminais.get(count));
            count++;
            noV = nosVisitadosTerminais.get(count);
        }
        TypeVar type = calculaValorExpressao(nosIf, null);

        if (type != TypeVar.Boolean) {
            NoArvoreSintatica no = nosIf.get(nosIf.size() - 1);
            throw new SemanticoException("Esperado um valor booleano na condição do: " + command, no.getLinha(), no.getColuna(), no.getNoDescricao());

        }
        return count;
    }

    private int analisaVariavel(int count) throws SemanticoException {
        int aux = count;
        ArrayList<NoArvoreSintatica> nosVar = new ArrayList<NoArvoreSintatica>();
        NoArvoreSintatica noV = nosVisitadosTerminais.get(count);
        nosVar.add(noV);
        boolean isAtribuicao = false;
        while (!isAtribuicao) {
            count++;
            noV = nosVisitadosTerminais.get(count);
            nosVar.add(noV);
            if (noV.getNoDescricao().equals("=")) {
                isAtribuicao = true;
            } else if (nosVar.get(nosVar.size() - 2).getTypeNo() == TypeNo.Var) {
                if (!noV.getNoDescricao().equals(",")) {
                    break;
                }
            } else if (nosVar.get(nosVar.size() - 2).getNoDescricao().equals(",")) {
                if (noV.getTypeNo() != TypeNo.Var) {
                    break;
                }
            }
        }
        if (isAtribuicao) {
            return analisaAtribuicao(aux);
        } //se não for atribuicao, vai buscar se o atributo já foi declarado
        else {
            AtributoTabela att = tabelaSemantica.getAtributo(nosVisitadosTerminais.get(aux));
            if (att instanceof AtributoTabelaFunction) {
                checkFunction(nosVisitadosTerminais, nosVisitadosTerminais.get(aux), aux, att);
                return aux;
            } else {
                return aux + 1;
            }

        }


    }

    private int analisaAtribuicao(int count) throws SemanticoException {
        boolean local = false;
        int escopo = 1;
        if (count > 0 && nosVisitadosTerminais.get(count - 1).getNoDescricao().equals("local")) {
            local = true;
            escopo = tabelaSemantica.getEscopoAtual();
        }
        NoArvoreSintatica no = nosVisitadosTerminais.get(count);
        ArrayList<NoArvoreSintatica> nosAtribuicao = new ArrayList<NoArvoreSintatica>();


        //busca todos os parâmetros
        while (!no.getNoDescricao().equals("=")) {
            if (no.getTypeNo() == TypeNo.Var) {
                nosAtribuicao.add(no);
            }
            count++;
            no = nosVisitadosTerminais.get(count);
        }
        count++;
        no = nosVisitadosTerminais.get(count);
        int quantVar = nosAtribuicao.size();
        int quantParenteses = 0;
        ArrayList<TypeVar> types = new ArrayList<TypeVar>();
        ArrayList<NoArvoreSintatica> nosAnalisar = new ArrayList<NoArvoreSintatica>();
        while (true) {
            if (types.size() <= quantVar) {
                if (no.getNoDescricao().equals("(")) {
                    quantParenteses++;
                } else if (no.getNoDescricao().equals(")")) {
                    quantParenteses--;
                }
                if (no.getNoDescricao().equals(",") && quantParenteses == 0) {
                    types.add(calculaValorExpressao(nosAnalisar, null));
                    nosAnalisar.clear();
                } else {
                    nosAnalisar.add(no);
                }
            }

            if (!operadoresAritmeticos.contains(no.getNoDescricao()) && !operadoresBooleanos.contains(no.getNoDescricao()) && !operadoresRelacionais.contains(no.getNoDescricao()) && !no.getNoDescricao().equals(",") && quantParenteses == 0) {
                if (count + 1 < nosVisitadosTerminais.size()) {
                    NoArvoreSintatica proxNo = nosVisitadosTerminais.get(count + 1);
                    if (!operadoresAritmeticos.contains(proxNo.getNoDescricao()) && !operadoresBooleanos.contains(proxNo.getNoDescricao()) && !operadoresRelacionais.contains(proxNo.getNoDescricao()) && !proxNo.getNoDescricao().equals(",") && !proxNo.getNoDescricao().equals("(")) {
                        types.add(calculaValorExpressao(nosAnalisar, null));
                        nosAnalisar.clear();
                        break;
                    }

                } else {
                    types.add(calculaValorExpressao(nosAnalisar, null));
                    nosAnalisar.clear();
                    break;
                }
            }
            count++;
            no = nosVisitadosTerminais.get(count);
        }
        for (int x = 0; x < nosAtribuicao.size(); x++) {
            TypeVar type;
            if (x < types.size()) {
                type = types.get(x);
            } else {
                type = TypeVar.Nil;
            }
            AtributoTabela atributo = new AtributoTabela(type, escopo, nosAtribuicao.get(x).getLinha(), nosAtribuicao.get(x).getColuna(), nosAtribuicao.get(x).getNoDescricao());
            tabelaSemantica.insertAtributo(atributo, local);
        }

        return count;
    }

    private int analisaFuncao(int count) throws SemanticoException {
        int escopo = 1;
        boolean local = false;
        if (count > 0 && nosVisitadosTerminais.get(count - 1).getNoDescricao().equals("local")) {
            escopo = tabelaSemantica.getEscopoAtual();
            local = true;
        }

        count++;
        NoArvoreSintatica no = nosVisitadosTerminais.get(count);
        int linha = no.getLinha();
        int coluna = no.getColuna();
        String nameVar = no.getNoDescricao();
        int quantidadeParametros = 0;
        count += 2;
        no = nosVisitadosTerminais.get(count);
        ArrayList<NoArvoreSintatica> nosFunc = new ArrayList<NoArvoreSintatica>();
        while (!no.getNoDescricao().equals(")")) {
            if (no.getTypeNo() == TypeNo.Var) {
                quantidadeParametros++;
                nosFunc.add(no);
            }
            count++;
            no = nosVisitadosTerminais.get(count);
        }

        AtributoTabelaFunction atributoTabelaFunction = new AtributoTabelaFunction(quantidadeParametros, TypeVar.Function, escopo, linha, coluna, nameVar);
        tabelaSemantica.insertAtributo(atributoTabelaFunction, local);
        tabelaSemantica.incrementaEscopo();
        for (int x = 0; x < nosFunc.size(); x++) {
            AtributoTabela att = new AtributoTabela(TypeVar.Especial, tabelaSemantica.getEscopoAtual(), nosFunc.get(x).getLinha(), nosFunc.get(x).getColuna(), nosFunc.get(x).getNoDescricao());
            tabelaSemantica.insertAtributo(att, true);
        }

        return count;
    }

    private int analisaReturn(int count) throws SemanticoException {
        count++;
        NoArvoreSintatica no = nosVisitadosTerminais.get(count);
        ArrayList<NoArvoreSintatica> nosRetorno = new ArrayList<NoArvoreSintatica>();
        while (!no.getNoDescricao().equals("end")) {
            nosRetorno.add(no);
            count++;
            no = nosVisitadosTerminais.get(count);
        }
        TypeVar type = calculaValorExpressao(nosRetorno, null);
        tabelaSemantica.atualizaValorRetornoFuncao(type);
        return count - 1;

    }

    public void analiseSemantica() throws SemanticoException {
        percorrePreOrdemArvore(arvoreSintatica.getNoRaiz());
        boolean flagFor = false;
        boolean flagWhile = false;
        for (int count = 0; count < nosVisitadosTerminais.size(); count++) {
            NoArvoreSintatica no = nosVisitadosTerminais.get(count);
            if (no.getNoDescricao().equals("if")) {
                tabelaSemantica.incrementaEscopo();
                count = analisaIf(count, "if");
            } else if (no.getNoDescricao().equals("elseif")) {
                tabelaSemantica.removeEscopo();
                count = analisaIf(count, "if");
            } else if (no.getNoDescricao().equals("else")) {
                tabelaSemantica.removeEscopo();
                tabelaSemantica.incrementaEscopo();
            } else if (no.getNoDescricao().equals("for")) {
                flagFor = true;
                count = analisaFor(count);
                tabelaSemantica.incrementaEscopo();
            } else if (no.getNoDescricao().equals("while")) {
                flagWhile = true;
                tabelaSemantica.incrementaEscopo();
            } else if (no.getNoDescricao().equals("repeat")) {
                tabelaSemantica.incrementaEscopo();
            } else if (no.getNoDescricao().equals("function")) {
                count = analisaFuncao(count);

            } else if (no.getNoDescricao().equals("do")) {
                if (flagWhile) {
                    flagWhile = false;
                } else if (flagFor) {
                    flagFor = false;
                } else {
                    tabelaSemantica.incrementaEscopo();
                }
            } else if (no.getNoDescricao().equals("end") || no.getNoDescricao().equals("until")) {
                tabelaSemantica.removeEscopo();
            } else if (no.getNoDescricao().equals("return")) {
                count = analisaReturn(count);
            } else if (no.getTypeNo() == TypeNo.Var) {
                count = analisaVariavel(count);
            }


        }
    }

    //Percorre pré ordem e adiciona somente os nós terminais, na lista de nos 
    private void percorrePreOrdemArvore(NoArvoreSintatica no) {
        ArrayList<NoArvoreSintatica> nos = no.getNoFilhos();
        if (no.isTerminal()) {
            nosVisitadosTerminais.add(no);
        }
        if (nos.size() > 0) {
            for (int count = 0; count < nos.size(); count++) {
                percorrePreOrdemArvore(nos.get(count));
            }

        }
    }
}
